<!DOCTYPE html>
<meta charset=utf-8>
<title>Test element-based scroll offset for scroll timeline.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/web-animations/testcommon.js"></script>
<script src="testcommon.js"></script>

<style>
  .scroller {
    overflow: auto;
    height: 500px;
    width: 500px;
    will-change: transform;
  }

  .contents {
    height: 2000px;
    width: 2000px;
    position: relative;
  }

  .vertical #start, .vertical #end {
    background: blue;
    border-top: 5px solid pink;
    box-sizing: border-box;
    width: 100%;
    height: 50px;
  }

  .vertical #start {
    position: absolute;
    top: 50px;
  }

  .vertical #end {
    position: absolute;
    top: 1050px;
  }

  .horizontal #start, .horizontal #end {
    background: blue;
    border-left:5px solid pink;
    box-sizing: border-box;
    height: 100%;
    width: 50px;
  }

  .horizontal #start {
    position: absolute;
    left: 50px;
  }

  .horizontal #end {
    position: absolute;
    left: 1050px;
  }
</style>
<div id="log"></div>
<script>
  'use strict';

  async function createScrollAnimationTest(description, config) {
    promise_test(async t => {
      const scroller = createScrollerWithStartAndEnd(t, config.orientation);
      t.add_cleanup(() => scroller.remove());

      const start = scroller.querySelector("#start");
      const end = scroller.querySelector("#end");

      // Force layout to ensure the scroll timeline gets the correct scroll
      // range.
      start.offsetHeight;

      const timeline = createScrollTimeline(t, {
        scrollSource: scroller,
        orientation: config.orientation,
        timeRange: 1000,
        fill: 'both',
        scrollOffsets:
            [{target: start, ...config.start}, {target: end, ...config.end }]
      });

      // Wait for new animation frame  which allows the timeline to compute new
      // current time.
      await waitForNextFrame();

      const animation = createScrollLinkedAnimation(t, timeline);
      const scrollRange = end.offsetTop - start.offsetTop;
      const timeRange = animation.timeline.timeRange;

      // Verify initial start and current times in Idle state.
      assert_equals(animation.currentTime, null,
        "The current time is null in Idle state.");
      assert_equals(animation.startTime, null,
        "The start time is null in Idle state.");

      animation.play();
      assert_true(animation.pending, "Animation is in pending state.");
      // Verify initial start and current times in Pending state.
      assert_times_equal(animation.currentTime, 0,
        "The current time is zero in Pending state.");
      assert_equals(animation.startTime, 0,
        "The start time is zero in Pending state.");

      await animation.ready;
      // Verify initial start and current times in Playing state.
      assert_times_equal(animation.currentTime, 0,
        "The current time is zero in Playing state.");
      assert_times_equal(animation.startTime, 0,
        "The start time is zero in Playing state.");

      // Now do some scrolling and make sure that the Animation current time is
      // correct.
      if (config.orientation == 'vertical') {
        scroller.scrollTo({top: config.scrollTo});
        assert_equals(scroller.scrollTop, config.scrollTo);
      } else {
        scroller.scrollTo({left: config.scrollTo});
        assert_equals(scroller.scrollLeft, config.scrollTo);
      }

      await waitForNextFrame();
      assert_times_equal(animation.timeline.currentTime, config.expectedCurrentTime,
        "The timeline current time corresponds to the scroll position of the scroller.");
      assert_times_equal(animation.currentTime, config.expectedCurrentTime,
        "The animation current time corresponds to the scroll position of the scroller.");
      assert_times_equal(
        animation.effect.getComputedTiming().localTime,
        config.expectedCurrentTime,
        'Effect local time corresponds to the scroll position of the scroller.');
    }, description);
  }

  // start is @   50px
  // end is   @   1050px
  // both have    50px heights
  // scroller has 500px heights
  // For each test the expected start/end is in the comment to help with the
  // verification.
  const tests = {
    // offsets: [100, 1100]
    "at start": {
      scrollTo: 100,
      expectedCurrentTime: 0,
    },
    // offsets: [100, 1100]
    "after start": {
      scrollTo: 200,
      expectedCurrentTime: 100,
    },
    // offsets: [100, 1100]
    "at middle" : {
      scrollTo: 600,
      expectedCurrentTime: 500,
    },
    // offsets: [100, 1100]
    "at end" : {
      scrollTo: 1099,
      expectedCurrentTime: 999,
    },
    // offsets: [100, 1100]
    "after end" : {
      scrollTo: 1150,
      expectedCurrentTime: 1000,
    },
    // offsets: [75, 1075]
    "with threshold 0.5" : {
      // give threshold to both start and end to keep scrollRange
      // 1000 which simplifies the calculation.
      start: {threshold: 0.5},
      end: {threshold: 0.5},
      scrollTo: 600 - 25,
      expectedCurrentTime: 500,
    },
    // offsets: [50, 1050]
    "with threshold 1.0": {
      start: {threshold: 1.0},
      end: {threshold: 1.0},
      scrollTo: 600 - 50,
      expectedCurrentTime: 500,
    },
    // offset: [100, 550]
    "with end edge" : {
      end: {edge: "end"},
      scrollTo: 325,
      expectedCurrentTime: 500,
    },
    // offset: [100, 600]
     "with end edge and threshold 1.0": {
      end: {
        threshold: 1.0,
        edge: "end"
      },
      scrollTo: 350,
      expectedCurrentTime: 500,
    },
  };

  for (let orientation of ['vertical', 'horizontal']) {
    for (let testName in tests) {
      const description = `Animation start and current times are correct given
          element-based offsets for orienation ${orientation} and ${testName}.`;
      const config = tests[testName];
      config.orientation = orientation;
      createScrollAnimationTest(description, config);
    }
  }
</script>
